// Copyright (C) 2005 Taylor L. Riché <riche@cs.utexas.edu>
//  
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software 
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// 	$Id: main.cc 257 2005-10-03 17:15:04Z richetl $	
//

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <assert.h>
#include <iostream>
#include <list>
#include <stdexcept>
#include <cstdlib>
#include <pthread.h>

#include "ApplicationGraph.hh"
#include "cmdline.h"
#include "commands.hh"
#include "CourierUnit.hh"
#include "DynamicLib.hh"
#include "ProcUnit.hh"
#include "Reader.hh"
#include "Scheduler.hh"
#include "TraceInterface.hh"

void printWelcome();
void printCommandHelp();
dlpp::DynamicLib<lee::ApplicationGraph *,int> * loadModule(std::string filename) throw(dlpp::DynamicLibException);
void unloadModule(dlpp::DynamicLib<lee::ApplicationGraph *,int> * lib) throw(dlpp::DynamicLibException);
std::string trim(const std::string& s);
void startProcs(int n, std::list<lee::ProcUnit *> & plist,lee::Scheduler & s);
void startCouriers(int n, std::map<pthread_t, lee::CourierUnit *> & cmap, lee::Scheduler & s);

static const std::string PPA_EXT = ".lapp";

int
main(int argc, char ** argv)
{

  gengetopt_args_info args_info;
  bool modLoaded;
  bool ranOnce = false;
  dlpp::DynamicLib<lee::ApplicationGraph *,int> * module = NULL;
  lee::ApplicationGraph * ag;
  
  if(cmdline_parser(argc, argv, &args_info) != 0) {
    std::exit(1);
  }
  
  printWelcome();

  if(args_info.inputs_num == 0) {
    modLoaded = false;
  }
  else if(args_info.inputs_num == 1) {
    try {
      module = loadModule(trim(args_info.inputs[0]));
      modLoaded = true;
    }
    catch(std::exception & e) {
      std::cerr << "MOD LOAD FAILED: " << e.what() << std::endl;
      modLoaded = false;
    }
  }
  else {
    std::cerr << "EXITING: Invalid number of file arguments." << std::endl;
    std::exit(1);
  }

  if(!args_info.trace_given) {
    std::cerr << "Network device mode is not supported at this time. Please use trace mode." 
	      << std::endl;
    std::exit(1);
  }
  int numPkts = 1;
  if(args_info.num_packets_given) {
    numPkts = args_info.num_packets_arg;
  }
  lee::TraceInterface eth0(lee::ETH0, lee::TSH, numPkts);
  lee::TraceInterface eth1(lee::ETH1, lee::TSH, numPkts);
  lee::TraceInterface eth2(lee::ETH2, lee::TSH, numPkts);
  lee::TraceInterface eth3(lee::ETH3, lee::TSH, numPkts);
 

  int numThreads = 1;
  if(args_info.num_threads_given) {
    numThreads = args_info.num_threads_arg;
  }
  lee::Scheduler scheduler;
  std::list<lee::ProcUnit *> plist;
  startProcs(numThreads, plist, scheduler);

  int numCouriers = 1;
  std::map<pthread_t, lee::CourierUnit *> cmap;
  if(args_info.num_couriers_given) {
    numCouriers = args_info.num_couriers_arg;
  }
  startCouriers(numCouriers, cmap, scheduler);

  reader::Reader appReader;
  while(true) {
    std::string lineRead;
    lineRead = appReader.getLine();

    // quit command

    if(lineRead.find(commands::CMD_QUIT,0) == 0) {
      if(modLoaded) {
	unloadModule(module);
      }
      for(std::list<lee::ProcUnit *>::iterator i = plist.begin();
	  i != plist.end(); i++) {
	(*i)->kill();
      }
      for(std::map<pthread_t, lee::CourierUnit *>::iterator i = cmap.begin();
	  i != cmap.end(); i++) {
	i->second->kill();
      }
      break;
    }

    // help command

    else if(lineRead.find(commands::CMD_HELP,0) == 0) {
      printCommandHelp();
    }

    // unload command

    else if(lineRead.find(commands::CMD_UNLOAD,0) == 0) {
      if(modLoaded) {
	try {
	  unloadModule(module);
	}
	catch(std::exception & e) {
	  std::cerr << e.what() << std::endl;
	}
	modLoaded = false;
      }
      else {
	std::cerr << "No application loaded." << std::endl;
      }
    }

    // load command

    else if(lineRead.find(commands::CMD_LOAD,0) == 0) {
      if(modLoaded) {
	std::cerr << "Application still loaded. Please use \"unload\" first."
		  << std::endl;
      }
      else {
	try {
	  module = loadModule(trim(lineRead.substr(commands::CMD_LOAD.size() + 1)));
	  modLoaded = true;
	}
	catch(std::exception & e) {
	  std::cerr << "MOD LOAD FAILED: " << e.what() << std::endl;
	  modLoaded = false;
	}
      }
    }

    // run command
    
    else if(lineRead.find(commands::CMD_RUN,0) == 0) {
      if(modLoaded && !ranOnce) {
	dlpp::DynamicLib<lee::ApplicationGraph *, int>::ret_ptr getAG;
	getAG = module->getSymbol("getAG");
	ag = (*getAG)(0);
	ag->setSched(&scheduler);
	eth0.setSched(&scheduler);
	eth0.setAG(ag);
	eth1.setSched(&scheduler);
	eth1.setAG(ag);
	eth2.setSched(&scheduler);
	eth2.setAG(ag);
	eth3.setSched(&scheduler);
	eth3.setAG(ag);
	ag->callInit();
	ag->setCMap(cmap);
	lee::Subscription * sub = dynamic_cast<lee::Subscription *>(ag);
	assert(sub != NULL);
	sub->setMap(&cmap);
	sub->registerSubs(sub);
	for(std::list<lee::ProcUnit *>::iterator i = plist.begin();
	    i != plist.end(); i++) {
	  (*i)->setAG(ag);
	}
	for(std::map<pthread_t, lee::CourierUnit *>::iterator i = cmap.begin();
	    i != cmap.end(); i++) {
	  i->second->setMaster(sub);
	}
	scheduler.schedule(plist, (*ag));
	assert(args_info.trace_given); 
	ranOnce = true;
      }
      else if(ranOnce) {
	std::cerr << "The application was already run. Only one run is supported at this time." << std::endl;
      }
      else {
	std::cerr << "No application is loaded. Please use the \"load\" command." << std::endl;
      }
    }

    // setv6 command

    else if(lineRead.find(commands::CMD_SETV6,0) == 0) {
      int ifNum = atol(&lineRead[6]);
      if(ranOnce) {
 	std::cerr << "The application was already run. Devices can only be changed before running." << std::endl;
     }
      else {
	if(ifNum == lee::NetInterface::ETH0int) {
	  eth0.setv6();
	}
	else if(ifNum == lee::NetInterface::ETH1int) {
	  eth1.setv6();
	}
	else if(ifNum == lee::NetInterface::ETH2int) {
	  eth2.setv6();
	}
	else if(ifNum == lee::NetInterface::ETH3int) {
	  eth3.setv6();
	}
	else {
	  std::cerr << "That device number does not exit." << std::endl;
	}
      }
    }

    else if(lineRead.empty()) {}

    // default

    else {
      std::cout << "Uknown command \"" << lineRead << "\" try \"help\"." << std::endl;
    }
  }

  return 0;
}

void
printWelcome()
{
  std::cout << "Welcome to the Lagniappe Execution Environment ("
	    << PACKAGE << " v" << VERSION << ")" << std::endl;
  std::cout << "Copyright (C) 2005 Taylor L. Riché <"<< PACKAGE_BUGREPORT << ">" << std::endl;
  std::cout << std::endl;
  std::cout << "LEE is free software, covered by the GNU General Public License, and you are" << std::endl;
  std::cout << "welcome to change it and/or distribute copies of it under certain conditions." << std::endl;
  std::cout << std::endl;
  std::cout << "Enter \"help\" for assistance." << std::endl;
  return;
}

void
printCommandHelp() 
{
  std::cout << std::endl;
  std::cout << "load <filename.ppa>\tLoads the application specified by filename.ppa."
	    << std::endl;
  std::cout << "unload\t\t\tUnloads the currently loaded application and \n\t\t\tresets the system."
	    << std::endl;
  std::cout << "run\t\t\tStarts the system with the currently loaded \n\t\t\tapplication."
	    << std::endl;
  std::cout << "help\t\t\tDisplys this message." << std::endl;
  std::cout << "quit\t\t\tQuits the Lagniappe Execution Environment.\n" << std::endl;
  return;
}

dlpp::DynamicLib<lee::ApplicationGraph *,int> * 
loadModule(std::string filename) throw(dlpp::DynamicLibException)
{
  dlpp::DynamicLib<lee::ApplicationGraph *,int> * newLib = NULL;
  //if(filename.substr(filename.size() - PPA_EXT.size()).compare(PPA_EXT) == 0) {
  newLib = new dlpp::DynamicLib<lee::ApplicationGraph *,int>(filename);
  //}
  //else {
  //throw dlpp::DynamicLibException("Can only load valid PPA files.");
  //}
  return newLib;
}

void
unloadModule(dlpp::DynamicLib<lee::ApplicationGraph *,int> * lib) throw(dlpp::DynamicLibException)
{
  if(lib != NULL) {
    delete lib;
  }
  else {
    throw dlpp::DynamicLibException("No application loaded.");
  }
  return;
}

inline std::string 
trim(const std::string& s) {
  if(s.length() == 0)
    return s;
  int b = s.find_first_not_of(" \t");
  int e = s.find_last_not_of(" \t");
  if(b == -1) // No non-spaces
    return "";
  return std::string(s, b, e - b + 1);
}

void
startProcs(int n, std::list<lee::ProcUnit *> & plist, lee::Scheduler & s)
{
  for(int i = 0; i < n; ++i) {
    plist.push_back(new lee::ProcUnit(&s));
  }
  return;
}

void
startCouriers(int n, std::map<pthread_t, lee::CourierUnit *> & cmap, 
	      lee::Scheduler & s)
{
  for(int i = 0; i < n; ++i) {
    new lee::CourierUnit(cmap, &s);
  }
}
